/* mpiutil.ac  -  Utility functions for MPI */

#include "mpi-internal.h"
#ifdef __KERNEL__
#include <linux/slab.h>
#endif
/* Constants allocated right away at startup.  */
static MPI constants[MPI_NUMBER_OF_CONSTANTS];

/* Initialize the MPI subsystem.  This is called early and
allows to
 * do some initialization without taking care of threading
issues.
*/
int mpi_init(void)
{
    int           idx;
    unsigned long value;

    for (idx = 0; idx < MPI_NUMBER_OF_CONSTANTS; idx++) {
        switch (idx) {
            case MPI_C_ZERO:
                value = 0;
                break;
            case MPI_C_ONE:
                value = 1;
                break;
            case MPI_C_TWO:
                value = 2;
                break;
            case MPI_C_THREE:
                value = 3;
                break;
            case MPI_C_FOUR:
                value = 4;
                break;
            case MPI_C_EIGHT:
                value = 8;
                break;
            default:
                mpi_log("MPI: invalid mpi_const selector %d\n", idx);
                return -EFAULT;
        }
        constants[idx]        = mpi_alloc_set_ui(value);
        constants[idx]->flags = (16 | 32);
    }

    return 0;
}

/* make sure `mpi_init` is called before use mpi */
#ifndef __KERNEL__
__attribute((constructor)) void pre_mpi_init() { mpi_init(); }
#endif

/* Return a constant MPI descripbed by NO which is one of
 * the MPI_C_xxx macros.  There is no need to copy this
 * returned value; it may be used directly.
 */
MPI mpi_const(enum gcry_mpi_constants no)
{
    if ((int)no < 0 || no > MPI_NUMBER_OF_CONSTANTS)
        mpi_log("MPI: invalid mpi_const selector %d\n", no);
    if (!constants[no]) mpi_log("MPI: MPI subsystem not initialized\n");
    return constants[no];
}

/****************
 * Note:  It was a bad idea to use the number of limbs to
 *allocate because on a alpha the limbs are large but we
 *normally need integers of n bits - So we should change
 *this to bits (or bytes).
 *
 *	  But mpi_alloc is used in a lot of places :-)
 */
MPI mpi_alloc(unsigned nlimbs)
{
    MPI a;

    a = mpimem_malloc(sizeof *a);
    if (!a) return a;

    if (nlimbs) {
        a->d = mpi_alloc_limb_space(nlimbs);
        if (!a->d) {
            mpimem_free(a);
            return NULL;
        }
    } else {
        a->d = NULL;
    }

    a->alloced = nlimbs;
    a->nlimbs  = 0;
    a->sign    = 0;
    a->flags   = 0;
    a->nbits   = 0;
    return a;
}

mpi_ptr_t mpi_alloc_limb_space(unsigned nlimbs)
{
    size_t len = nlimbs * sizeof(mpi_limb_t);

    if (!len) return NULL;

    return mpimem_malloc(len);
}

void mpi_free_limb_space(mpi_ptr_t a)
{
    if (!a) return;

    mpimem_free(a);
}

void mpi_assign_limb_space(MPI a, mpi_ptr_t ap, unsigned nlimbs)
{
    mpi_free_limb_space(a->d);
    a->d       = ap;
    a->alloced = nlimbs;
}

/****************
 * Resize the array of A to NLIMBS. the additional space is
 * cleared (set to 0) [done by m_realloc()]
 */
int mpi_resize(MPI a, unsigned nlimbs)
{
    void* p;

    if (nlimbs <= a->alloced) return 0; /* no need to do it */

    if (a->d) {
        p = mpimem_calloc(nlimbs, sizeof(mpi_limb_t));
        if (!p) return -ENOMEM;
        memcpy(p, a->d, a->alloced * sizeof(mpi_limb_t));
        mpimem_free(a->d);
        a->d = p;
    } else {
        a->d = mpimem_calloc(nlimbs, sizeof(mpi_limb_t));
        if (!a->d) return -ENOMEM;
    }
    a->alloced = nlimbs;
    return 0;
}

void mpi_clear(MPI a)
{
    if (!a) return;
    a->nlimbs = 0;
    a->flags  = 0;
}

void mpi_free(MPI a)
{
    if (!a) return;

    if (a->flags & 4)
        mpimem_free(a->d);
    else
        mpi_free_limb_space(a->d);

    if (a->flags & ~7) mpi_log("invalid flag value in mpi\n");
    mpimem_free(a);
    a = NULL;
}

/****************
 * Note: This copy function should not interpret the MPI
 *	 but copy it transparently.
 */
MPI mpi_copy(MPI a)
{
    int i;
    MPI b;

    if (a) {
        b         = mpi_alloc(a->nlimbs);
        b->nlimbs = a->nlimbs;
        b->sign   = a->sign;
        b->flags  = a->flags;
        b->flags &= ~(16 | 32); /* Reset the immutable and
                                   constant flags. */
        for (i = 0; i < b->nlimbs; i++) b->d[i] = a->d[i];
    } else
        b = NULL;
    return b;
}

/****************
 * This function allocates an MPI which is optimized to hold
 * a value as large as the one given in the argument and
 * allocates it with the same flags as A.
 */
MPI mpi_alloc_like(MPI a)
{
    MPI b;

    if (a) {
        b         = mpi_alloc(a->nlimbs);
        b->nlimbs = 0;
        b->sign   = 0;
        b->flags  = a->flags;
    } else
        b = NULL;

    return b;
}

/* Set U into W and release U.  If W is NULL only U will be
 * released. */
void mpi_snatch(MPI w, MPI u)
{
    if (w) {
        mpi_assign_limb_space(w, u->d, u->alloced);
        w->nlimbs  = u->nlimbs;
        w->sign    = u->sign;
        w->flags   = u->flags;
        u->alloced = 0;
        u->nlimbs  = 0;
        u->d       = NULL;
    }
    mpi_free(u);
}

MPI mpi_set(MPI w, MPI u)
{
    mpi_ptr_t  wp, up;
    mpi_size_t usize = u->nlimbs;
    int        usign = u->sign;

    if (!w) w = mpi_alloc(mpi_get_nlimbs(u));
    RESIZE_IF_NEEDED(w, usize);
    wp = w->d;
    up = u->d;
    MPN_COPY(wp, up, usize);
    w->nlimbs = usize;
    w->flags  = u->flags;
    w->flags &= ~(16 | 32); /* Reset the immutable and constant flags.  */
    w->sign = usign;
    return w;
}

MPI mpi_set_ui(MPI w, unsigned long u)
{
    if (!w) w = mpi_alloc(1);
    /* FIXME: If U is 0 we have no need to resize and thus
     * possible allocating the the limbs.
     */
    RESIZE_IF_NEEDED(w, 1);
    w->d[0]   = u;
    w->nlimbs = u ? 1 : 0;
    w->sign   = 0;
    w->flags  = 0;
    return w;
}

MPI mpi_alloc_set_ui(unsigned long u)
{
    MPI w     = mpi_alloc(1);
    w->d[0]   = u;
    w->nlimbs = u ? 1 : 0;
    w->sign   = 0;
    return w;
}

/****************
 * Swap the value of A and B, when SWAP is 1.
 * Leave the value when SWAP is 0.
 * This implementation should be constant-time regardless of
 * SWAP.
 */
void mpi_swap_cond(MPI a, MPI b, unsigned long swap)
{
    mpi_size_t i;
    mpi_size_t nlimbs;
    mpi_limb_t mask = ((mpi_limb_t)0) - swap;
    mpi_limb_t x;

    if (a->alloced > b->alloced)
        nlimbs = b->alloced;
    else
        nlimbs = a->alloced;
    if (a->nlimbs > nlimbs || b->nlimbs > nlimbs) return;

    for (i = 0; i < nlimbs; i++) {
        x       = mask & (a->d[i] ^ b->d[i]);
        a->d[i] = a->d[i] ^ x;
        b->d[i] = b->d[i] ^ x;
    }

    x         = mask & (a->nlimbs ^ b->nlimbs);
    a->nlimbs = a->nlimbs ^ x;
    b->nlimbs = b->nlimbs ^ x;

    x       = mask & (a->sign ^ b->sign);
    a->sign = a->sign ^ x;
    b->sign = b->sign ^ x;
}

int mpi_print_helper(MPI a)
{
    size_t         len;
    int            ret;
    unsigned char* buf = mpimem_malloc(MPI_NBYTES(a) * 2);
    if (!buf) return 1;
    len = MPI_NBYTES(a) * 2 + 1;
    ret = mpi_print(GCRYMPI_FMT_HEX, buf, len, &len, a);
    if (ret) goto out;
    mpi_log("0x%s\n", buf);
out:
    if (buf) mpimem_free(buf);
    return ret;
}

int mpi_print_helper_v2(MPI a)
{
    unsigned int   nbyte;
    int            sign;
    size_t         i;
    unsigned char* buf = mpi_get_buffer(a, &nbyte, &sign);
    if (!buf) return 1;
    mpi_log("0x");
    for (i = 0; i < nbyte; i++) {
        mpi_log("%02X", buf[i]);
        if ((i % 8) == 0 && i != 0) mpi_log(" ");
    }
    mpi_log("\n");
    mpimem_free(buf);
    return 0;
}

int mpi_print_helper_v3(const char* descript, MPI a)
{
    if (descript) mpi_log("%s:", descript);
    return mpi_print_helper_v2(a);
}

#ifndef __KERNEL__
#if BITS_PER_LONG == 32
int fls64(uint64_t x)
{
    uint32_t h = x >> 32;
    if (h) return fls(h) + 32;
    return fls(x);
}
#elif BITS_PER_LONG == 64
int fls64(uint64_t x)
{
    if (x == 0) return 0;
    return __fls(x) + 1;
}
#else
#error BITS_PER_LONG not 32 or 64
#endif

#ifdef __x86_64__
unsigned long __ffs(unsigned long word)
{
    asm("rep; bsf %1,%0" : "=r"(word) : "rm"(word));
    return word;
}
#else
unsigned long __ffs(unsigned long word)
{
    int num = 0;

#if BITS_PER_LONG == 64
    if ((word & 0xffffffff) == 0) {
        num += 32;
        word >>= 32;
    }
#endif
    if ((word & 0xffff) == 0) {
        num += 16;
        word >>= 16;
    }
    if ((word & 0xff) == 0) {
        num += 8;
        word >>= 8;
    }
    if ((word & 0xf) == 0) {
        num += 4;
        word >>= 4;
    }
    if ((word & 0x3) == 0) {
        num += 2;
        word >>= 2;
    }
    if ((word & 0x1) == 0) num += 1;
    return num;
}
#endif

#ifdef __x86_64__
unsigned long __fls(unsigned long word)
{
    asm("bsr %1,%0" : "=r"(word) : "rm"(word));
    return word;
}
#else
unsigned long __fls(unsigned long word)
{
    int num = BITS_PER_LONG - 1;

#if BITS_PER_LONG == 64
    if (!(word & (~0ul << 32))) {
        num -= 32;
        word <<= 32;
    }
#endif
    if (!(word & (~0ul << (BITS_PER_LONG - 16)))) {
        num -= 16;
        word <<= 16;
    }
    if (!(word & (~0ul << (BITS_PER_LONG - 8)))) {
        num -= 8;
        word <<= 8;
    }
    if (!(word & (~0ul << (BITS_PER_LONG - 4)))) {
        num -= 4;
        word <<= 4;
    }
    if (!(word & (~0ul << (BITS_PER_LONG - 2)))) {
        num -= 2;
        word <<= 2;
    }
    if (!(word & (~0ul << (BITS_PER_LONG - 1)))) num -= 1;
    return num;
}
#endif

void* ERR_PTR(long error) { return (void*)error; }

int fls(unsigned int x)
{
    int r = 32;

    if (!x) return 0;
    if (!(x & 0xffff0000u)) {
        x <<= 16;
        r -= 16;
    }
    if (!(x & 0xff000000u)) {
        x <<= 8;
        r -= 8;
    }
    if (!(x & 0xf0000000u)) {
        x <<= 4;
        r -= 4;
    }
    if (!(x & 0xc0000000u)) {
        x <<= 2;
        r -= 2;
    }
    if (!(x & 0x80000000u)) {
        x <<= 1;
        r -= 1;
    }
    return r;
}
#endif

void* mpimem_malloc(size_t size)
{
#ifdef __KERNEL__
    return kmalloc(size, GFP_KERNEL);
#else
    return malloc(size);
#endif
}

void* mpimem_calloc(size_t num, size_t size)
{
#ifdef __KERNEL__
    return kcalloc(num, size, GFP_KERNEL);
#else
    return calloc(num, size);
#endif
}

void mpimem_free(void* p)
{
#ifdef __KERNEL__
    return kfree(p);
#else
    return free(p);
#endif
}